package com.divillysausages.dsair.util
{
	import flash.utils.describeType;
	import flash.utils.getQualifiedClassName;
	
	/**
	 * This is adapted from Scott Bilas' Enum class, which you can find at
	 * http://scottbilas.com/blog/ultimate-as3-fake-enums/
	 */

	/**
	 * A class for emulating enums in Flash
	 * @author Damian Connolly
	 */
	public class Enum
	{
		
		/********************************************************************/

		private static var m_pendingDb:Object 	= new Object;	// typename -> [constants]
		private static var m_enumDb:Object 		= new Object;	// typename -> EnumConstants
		
		/********************************************************************/
		
		/**
		 * Get the enum for a particular class. Returns a copy
		 * @param clazz	The Class that we're looking for
		 * @return	The enum as a Vector array, or null if we can't find it
		 */
		public static function getEnum( clazz:Class ):Vector.<Enum>
		{
			var constants:EnumConstants = Enum.m_enumDb[getQualifiedClassName( clazz )];
			if ( constants == null )
				return null;

			// return a copy to prevent caller modifications
			return constants.byIndex.slice();
		}

		/**
		 * Get a single field of an Enum
		 * @param clazz			The Class that we're looking for
		 * @param name			The name of the field in that enum
		 * @param caseSensitive	If we want to be case sensitive or not
		 * @return	The field of a enum, or null if we can't find it
		 */
		public static function getField( clazz:Class, name:String, caseSensitive:Boolean = false ):Enum
		{
			// get our entire enum
			var constants:EnumConstants = Enum.m_enumDb[getQualifiedClassName( clazz )];
			if ( constants == null )
				return null;

			// look for the field - if we're using case sensitive, the check the name
			var field:Enum = constants.byName[name.toLowerCase()];
			if ( caseSensitive && ( field != null ) && ( name != field.name ) )
				return null;

			return field;
		}
		
		/**
		 * Get an enum based on an index
		 * @param clazz The Class that we're looking for
		 * @param index The index of the enum we're looking for
		 * @return The enum, or null if we can't find it
		 */
		public static function getByIndex( clazz:Class, index:int ):Enum
		{
			if ( index < 0 )
				return null;
				
			// get our entire enum
			var constants:EnumConstants = Enum.m_enumDb[getQualifiedClassName( clazz )];
			if ( constants == null )
				return null;
				
			// see if the index is good
			if ( index >= constants.byIndex.length )
				return null;
				
			// return the enum
			return constants.byIndex[index];
		}
		
		/********************************************************************/

		/**
		 * Inits the enum taking out all the static consts and striping their name and assigning
		 * an index
		 * @param clazz	The Class that this is for
		 */
		protected static function initEnum( clazz:Class ):void
		{
			var className:String = getQualifiedClassName( clazz );

			// can't call initEnum twice on same type (likely copy-paste bug)
			if ( Enum.m_enumDb[className] != undefined )
				throw new Error( "Can't initialize enum twice (type='" + className + "')");

			// no constant is technically ok, but it's probably a copy-paste bug
			var constants:Vector.<Enum> = Enum.m_pendingDb[className];
			if (constants == null)
				throw new Error( "Can't have an enum without any constants (type='" + className + "')");
				
			// fix the vector - there should be any more added to it
			constants.fixed = true;

			// process constants
			var type:XML = describeType( clazz );
			for each ( var constant:XML in type.constant )
			{
				// this will fail to coerce if the type isn't inherited from Enum
				var enumConstant:Enum = clazz[constant.@name];

				// if the types don't match then probably have a copy-paste error.
				// this is really common so it's good to catch it here.
				var enumConstantType:* = Object( enumConstant ).constructor;
				if ( enumConstantType != clazz )
					throw new Error( "Constant type '" + enumConstantType + "' does not match its enum class '" + clazz + "'");

				enumConstant.m_name = constant.@name;
			}

			// now seal it
			Enum.m_pendingDb[className] = null;
			Enum.m_enumDb[className] 	= new EnumConstants( constants );
		}
		
		/********************************************************************/

		private var m_name:String 	= null;	// the name of the enum field
		private var m_index:int 	= -1;	// the index of the enum field
		
		/********************************************************************/
		
		/**
		 * [read-only] The name of the enum field
		 */
		public function get name():String { return this.m_name; }
		
		/**
		 * [read-only] The index of the enum field
		 */
		public function get index():int { return this.m_index; }
		
		/********************************************************************/

		/**
		 * Creates our Enum field object
		 */
		public function Enum()
		{
			var typeName:String = getQualifiedClassName( this );

			// discourage people new'ing up constants on their own instead
			// of using the class constants
			if ( Enum.m_enumDb[typeName] != null )
				throw new Error( "Enum constants can only be constructed as static consts in their own enum class (bad type='" + typeName + "')" );

			// if opening up a new type, alloc an array for its constants
			var constants:Vector.<Enum> = Enum.m_pendingDb[typeName];
			if ( constants == null )
				Enum.m_pendingDb[typeName] = constants = new Vector.<Enum>;

			// record
			this.m_index = constants.length;
			constants.push( this );
		}

		/**
		 * Converts the Enum to a String
		 * @return	The String representation of the enum
		 */
		public function toString():String 
		{ 
			return this.m_name + " (" + this.m_index + ")"; 
		}
	}
}

import com.divillysausages.dsair.util.Enum

/**
 * Internal support class for EnumConstants
 */
internal class EnumConstants
{
	/**
	 * Creates our EnumConstants
	 * @param vec	The Vector array of our enums, in index order
	 */
	public function EnumConstants( vec:Vector.<Enum> )
	{
		this.byIndex = vec;

		// create our named object
		var len:int = this.byIndex.length;
		for ( var i:int = 0; i < len; i++ )
		{
			var enumConstant:Enum = this.byIndex[i];
			this.byName[enumConstant.name.toLowerCase()] = enumConstant;
		}
	}

	/**
	 * The enum constants array by index
	 */
	public var byIndex:Vector.<Enum> = null;
	
	/**
	 * The enum constants as an object
	 */
	public var byName:Object = new Object;
}
